using BlockOS.Client.Commands;
using BlockOS.Client.GUI;
using BlockOS.Client.Res;
using BlockOS.Console;
using System;
using System.Collections;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

namespace BlockOS.Client
{
    public sealed class GameClient : MonoSingleton<GameClient>, IGame
    {
        public EventBus EventBus { get; private set; }
        public GameConsole Console { get; private set; }
        public GameResource Res { get; private set; }
        public AppFiles Files { get; private set; }
        public WindowManager WindowManager => windowManager;

        private ServerList m_ServerList = null;
        public ServerList ServerList
        {
            get
            {
                m_ServerList ??= new ServerList(Files);
                return m_ServerList;
            }
        }

        private RenderTexture m_Framebuffer;
        public RenderTexture Framebuffer => m_Framebuffer;

        #region Inspectors
        public ClientWorld worldPrefab;
        public RawImage renderSurface;
        [SerializeField]
        private WindowManager windowManager;
        [SerializeField]
        private Toast toast;
        [SerializeField]
        private LoadingPage loadingPage;

        [SerializeField]
        private WindowDef[] windowDefinitions;

        public TerminalInterface terminal;
        public UnityEvent<string> onGameConsoleOutput;
        #endregion

        private AbstractClientStage currentStage;

        protected override void Awake()
        {
            base.Awake();
            RecreateFramebuffer();
            InitBasics();
        }

        private void Start()
        {
            StartCoroutine(Res.Reload(Enumerable.Empty<string>()));
            SetStage(new MenuStage());

            //ShowToast($"{(new TimeSpan(0, 0, 1) / 20).TotalSeconds}");
        }

        private void Update()
        {
            EventBus.ProcessEvents();
            currentStage.Update();
        }

        private void OnDestroy()
        {
            ServerList.Save(Files);
            Settings.Save();
        }
        private void InitBasics()
        {
            Settings.Load();
            EventBus = new EventBus();
            Res = new GameResource();
            Files = new AppFiles();
            #region Game Console Init
            Console = new GameConsole(this);
            Console.RegistCmd("echo", new EchoCommand());
            Console.RegistCmd("log", new LogCommand());
            Console.RegistCmd("say", new SayCommand());
            Console.RegistCmd("close", new CloseTerminalCommand());

            Console.messageReceiver += (msg) => onGameConsoleOutput?.Invoke(msg);
            #endregion
        }


        public void PushWindow(string name, WindowManager.PushWindowMode mode = WindowManager.PushWindowMode.Overlay)
        {
            windowManager.PushWindow(Array.Find(windowDefinitions, w => w.name == name)?.prefab, mode);
        }

        public void SetMainStage(IWorld world)
        {
            StartCoroutine(LoadWorld(world));
        }

        private IEnumerator LoadWorld(IWorld world)
        {
            loadingPage.Show();
            if (world != null) yield return world.Init(loadingPage.SetDescription);
            SetStage(new MainStage(world));
            loadingPage.Hide();
        }

        public void SetStage(AbstractClientStage stage)
        {
            currentStage?.Deinit();
            currentStage = stage;
            currentStage.client = this;
            currentStage.Init();
        }

        public void ShowToast(string message)
        {
            toast.Show(message);
        }

        public void ShowToast(string message, float duration)
        {
            toast.Show(message, duration);
        }

        public void RecreateFramebuffer()
        {
            var descriptor = new RenderTextureDescriptor(Screen.width, Screen.height);
            descriptor.depthBufferBits = 24;
            m_Framebuffer = new RenderTexture(descriptor)
            {
                name = "Framebuffer"
            };
            currentStage?.OnViewportChange();
        }

        public void Exit()
        {
            Debug.Log("App exit!");
            Application.Quit();
        }

        public GameConsole GetConsole() => Console;
        public TerminalInterface GetTerminal() => terminal;

        [Serializable]
        public class WindowDef
        {
            public string name;
            public Window prefab;
        }
    }
}
